package com.study.easyexcel.common;

import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.util.MapUtils;
import com.study.easyexcel.common.domain.AxisParam;
import com.study.easyexcel.common.domain.ItemSubject;
import com.study.easyexcel.common.function.AxisFunction;
import org.springframework.util.ObjectUtils;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Excel组合列表填充数据的封装流程。模板工具提供单行组数据的封装思路，一切可变因数应该在子类实现
 * 横向组：简称列组，也称横轴(x轴)组。在横轴把每列的数据进行分组
 * 纵向组：简称行组，也称纵轴(y轴)组。在纵轴把每行的数据进行分组
 *
 * @author yuhuan
 * @date 2023/7/4
 */
public abstract class RowGroupTemplate<T extends AxisParam> {

    /**
     * Excel填充纵向组名的取值键，占位格式{@code asset.subject}
     */
    protected static final String GROUP_NAME_KEY = "subject";

    /**
     * 横向填充的所有共享属性（规定横向组包含的属性必须相同）
     */
    protected List<String> keys;

    protected Map<String, String> xAxisGroups;

    protected Map<String, Map<String, String>> yAxisGroups;

    /**
     * 填充数据的来源，通常取自数据库
     */
    protected AxisFunction<T, Map<String, Object>> source;

    protected RowGroupTemplate(AxisFunction<T, Map<String, Object>> source) {
        this.source = source;
    }

    /**
     * 继承者应该在构造函数中调用初始化程序
     */
    protected void initData() {
        this.keys = this.includeColumn();
        this.xAxisGroups = this.groupByHorizontalAxis();
        this.yAxisGroups = this.groupByVerticalAxis();
    }

    public RowGroupTemplate<T> switchGroup(AxisFunction<T, Map<String, Object>> source) {
        this.source = source;
        return this;
    }

    /**
     * 获取纵向填充的单组数据，通过{@link RowGroupTemplate#bodyFromHorizontalGroup(String)}整合了每行的数据
     *
     * @param groups 行的分组
     * @return 包含了单个行组的所有数据（单行组数据）
     */
    public List<Map<String, Object>> bodyFromVerticalGroup(List<ItemSubject> groups) {
        List<Map<String, Object>> groupData = ListUtils.newArrayList();
        for (ItemSubject group : groups) {
            Map<String, Object> rowData = MapUtils.newHashMap();
            rowData.put(GROUP_NAME_KEY, group.getItemName());
            rowData.putAll(bodyFromHorizontalGroup(group.getItemId()));
            groupData.add(rowData);
        }
        return groupData;
    }

    /**
     * 将下划线风格的字符串转换为驼峰式（也称为小驼峰命名法）
     */
    public String convertToHumpStyle(String underscoreVariable) {
        StringBuilder camelCaseVariable = new StringBuilder();
        String[] words = underscoreVariable.split("_");
        for (int i = 0; i < words.length; i++) {
            if (i == 0) {
                camelCaseVariable.append(words[i]);
            } else {
                String firstLetter = words[i].substring(0, 1).toUpperCase();
                String restOfWord = words[i].substring(1).toLowerCase();
                camelCaseVariable.append(firstLetter).append(restOfWord);
            }
        }
        return camelCaseVariable.toString();
    }

    /**
     * 横向组包含的共同属性，由构造体初始化到{@link RowGroupTemplate#keys}；属性名不应该以驼峰式存在，如果是多个单
     * 词应该以下划线连接
     *
     * @return 包含横向组的共同属性（列名）
     */
    protected abstract List<String> includeColumn();

    protected abstract Map<String, String> groupByHorizontalAxis();

    protected abstract Map<String, Map<String, String>> groupByVerticalAxis();

    /**
     * 获取横向填充的分组数据，内部应该通过{@link RowGroupTemplate#regionBlockBody(AxisParam, String)}获取
     * 本行内列组的数据，然后合并所有列组的数据形成完整的行数据
     *
     * @param subject 行组内的行ID
     * @return 包含了一行的数据
     */
    protected abstract Map<String, Object> bodyFromHorizontalGroup(String subject);

    /**
     * 获取横轴和竖轴对应坐标区域的数据块
     *
     * @param param  横向和纵向轴的封装条件
     * @param prefix 区分横向组（横轴）的取值标记键
     */
    protected Map<String, Object> regionBlockBody(T param, String prefix) {
        Map<String, Object> columnData = MapUtils.newHashMap();
        Map<String, Object> dataBlock = this.source.query(param);
        for (String column : this.keys) {
            columnData.put(getKey(prefix, column), dataBlock.get(getKey("", column)));
        }
        return columnData;
    }

    /**
     * Excel填充的取值键
     *
     * @param prefix 区分横向组（横轴）的取值标记键
     * @param column 横向显示的属性，来源于{@link RowGroupTemplate#keys}
     */
    protected String getKey(String prefix, String column) {
        if (ObjectUtils.isEmpty(prefix)) {
            return column;
        }
        return convertToHumpStyle(MessageFormat.format("{0}_{1}", prefix, column));
    }

    public Map<String, String> getxAxisGroups() {
        return xAxisGroups;
    }

    public Map<String, Map<String, String>> getyAxisGroups() {
        return yAxisGroups;
    }

    public ArrayList<ItemSubject> getItemSubjects(String groupName) {
        ArrayList<ItemSubject> itemSubjects = ListUtils.newArrayList();
        Map<String, String> rowGroup = this.yAxisGroups.get(groupName);
        for (Map.Entry<String, String> row : rowGroup.entrySet()) {
            ItemSubject itemSubject = new ItemSubject();
            itemSubject.setItemId(row.getKey());
            itemSubject.setItemName(row.getValue());
            itemSubjects.add(itemSubject);
        }
        return itemSubjects;
    }
}
